package session

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"runtime"
	"time"

	"github.com/Tencent/WeKnora/internal/errors"
	"github.com/Tencent/WeKnora/internal/event"
	"github.com/Tencent/WeKnora/internal/logger"
	"github.com/Tencent/WeKnora/internal/types"
	secutils "github.com/Tencent/WeKnora/internal/utils"
	"github.com/gin-gonic/gin"
)

// SearchKnowledge performs knowledge base search without LLM summarization
func (h *Handler) SearchKnowledge(c *gin.Context) {
	ctx := logger.CloneContext(c.Request.Context())

	logger.Info(ctx, "Start processing knowledge search request")

	// Parse request body
	var request SearchKnowledgeRequest
	if err := c.ShouldBindJSON(&request); err != nil {
		logger.Error(ctx, "Failed to parse request data", err)
		c.Error(errors.NewBadRequestError(err.Error()))
		return
	}

	// Validate request parameters
	if request.Query == "" {
		logger.Error(ctx, "Query content is empty")
		c.Error(errors.NewBadRequestError("Query content cannot be empty"))
		return
	}

	if request.KnowledgeBaseID == "" {
		logger.Error(ctx, "Knowledge base ID is empty")
		c.Error(errors.NewBadRequestError("Knowledge base ID cannot be empty"))
		return
	}

	logger.Infof(
		ctx,
		"Knowledge search request, knowledge base ID: %s, query: %s",
		secutils.SanitizeForLog(request.KnowledgeBaseID),
		secutils.SanitizeForLog(request.Query),
	)

	// Directly call knowledge retrieval service without LLM summarization
	searchResults, err := h.sessionService.SearchKnowledge(ctx, request.KnowledgeBaseID, request.Query)
	if err != nil {
		logger.ErrorWithFields(ctx, err, nil)
		c.Error(errors.NewInternalServerError(err.Error()))
		return
	}

	logger.Infof(ctx, "Knowledge search completed, found %d results", len(searchResults))

	// Return search results
	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"data":    searchResults,
	})
}

// KnowledgeQA handles knowledge base question answering requests with LLM summarization
func (h *Handler) KnowledgeQA(c *gin.Context) {
	ctx := logger.CloneContext(c.Request.Context())

	logger.Info(ctx, "Start processing knowledge QA request")

	// Get session ID from URL parameter
	sessionID := secutils.SanitizeForLog(c.Param("session_id"))
	if sessionID == "" {
		logger.Error(ctx, "Session ID is empty")
		c.Error(errors.NewBadRequestError(errors.ErrInvalidSessionID.Error()))
		return
	}

	// Parse request body
	var request CreateKnowledgeQARequest
	if err := c.ShouldBindJSON(&request); err != nil {
		logger.Error(ctx, "Failed to parse request data", err)
		c.Error(errors.NewBadRequestError(err.Error()))
		return
	}

	// Create assistant message
	assistantMessage := &types.Message{
		SessionID:   sessionID,
		Role:        "assistant",
		RequestID:   c.GetString(types.RequestIDContextKey.String()),
		IsCompleted: false,
	}

	// Validate query content
	if request.Query == "" {
		logger.Error(ctx, "Query content is empty")
		c.Error(errors.NewBadRequestError("Query content cannot be empty"))
		return
	}

	logger.Infof(
		ctx,
		"Knowledge QA request, session ID: %s, query: %s",
		sessionID,
		secutils.SanitizeForLog(request.Query),
	)

	// Get session to prepare knowledge base IDs
	session, err := h.sessionService.GetSession(ctx, sessionID)
	if err != nil {
		logger.Errorf(ctx, "Failed to get session, session ID: %s, error: %v", sessionID, err)
		c.Error(errors.NewInternalServerError(err.Error()))
		return
	}

	// Prepare knowledge base IDs
	knowledgeBaseIDs := request.KnowledgeBaseIDs
	if len(knowledgeBaseIDs) == 0 && session.KnowledgeBaseID != "" {
		knowledgeBaseIDs = []string{session.KnowledgeBaseID}
		logger.Infof(
			ctx,
			"No knowledge base IDs in request, using session default: %s",
			secutils.SanitizeForLog(session.KnowledgeBaseID),
		)
	}

	// Use shared function to handle KnowledgeQA request
	h.handleKnowledgeQARequest(ctx, c, session, secutils.SanitizeForLog(request.Query),
		secutils.SanitizeForLogArray(knowledgeBaseIDs),
		assistantMessage, true, secutils.SanitizeForLog(request.SummaryModelID), request.WebSearchEnabled)
}

// AgentQA handles agent-based question answering with conversation history and streaming
func (h *Handler) AgentQA(c *gin.Context) {
	ctx := logger.CloneContext(c.Request.Context())
	logger.Info(ctx, "Start processing agent QA request")

	// Get session ID from URL parameter
	sessionID := secutils.SanitizeForLog(c.Param("session_id"))
	if sessionID == "" {
		logger.Error(ctx, "Session ID is empty")
		c.Error(errors.NewBadRequestError(errors.ErrInvalidSessionID.Error()))
		return
	}

	// Parse request body
	var request CreateKnowledgeQARequest
	if err := c.ShouldBindJSON(&request); err != nil {
		logger.Error(ctx, "Failed to parse request data", err)
		c.Error(errors.NewBadRequestError(err.Error()))
		return
	}
	if requestJSON, err := json.Marshal(request); err == nil {
		logger.Infof(ctx, "Agent QA request, request: %s", secutils.SanitizeForLog(string(requestJSON)))
	} else {
		logger.Warnf(ctx, "failed to marshal for logging: %s", secutils.SanitizeForLog(err.Error()))
	}

	// Validate query content
	if request.Query == "" {
		logger.Error(ctx, "Query content is empty")
		c.Error(errors.NewBadRequestError("Query content cannot be empty"))
		return
	}

	tenantInfo := ctx.Value(types.TenantInfoContextKey).(*types.Tenant)

	// Get session information first
	session, err := h.sessionService.GetSession(ctx, sessionID)
	if err != nil {
		logger.Errorf(ctx, "Failed to get session, session ID: %s, error: %v", sessionID, err)
		c.Error(errors.NewNotFoundError("Session not found"))
		return
	}
	sessionJSON, err := json.Marshal(session)
	if err != nil {
		logger.Errorf(ctx, "Failed to marshal session, session ID: %s, error: %v", sessionID, err)
		c.Error(errors.NewInternalServerError(err.Error()))
		return
	}
	logger.Infof(ctx, "Before AgentQA, Session: %s", secutils.SanitizeForLog(string(sessionJSON)))

	// Create assistant message
	assistantMessage := &types.Message{
		SessionID:   sessionID,
		Role:        "assistant",
		RequestID:   c.GetString(types.RequestIDContextKey.String()),
		IsCompleted: false,
	}

	// Initialize AgentConfig if it doesn't exist
	if session.AgentConfig == nil {
		session.AgentConfig = &types.SessionAgentConfig{}
	}

	// Detect if knowledge bases or agent mode has changed
	knowledgeBasesChanged := false
	configChanged := false

	// Check if knowledge bases array has changed
	if len(request.KnowledgeBaseIDs) > 0 {
		// Compare arrays to detect changes
		currentKBs := session.AgentConfig.KnowledgeBases
		if len(currentKBs) != len(request.KnowledgeBaseIDs) {
			knowledgeBasesChanged = true
			configChanged = true
		} else {
			// Check if contents are different
			kbMap := make(map[string]bool)
			for _, kb := range currentKBs {
				kbMap[kb] = true
			}
			for _, kb := range request.KnowledgeBaseIDs {
				if !kbMap[kb] {
					knowledgeBasesChanged = true
					configChanged = true
					break
				}
			}
		}
		if knowledgeBasesChanged {
			logger.Infof(ctx, "Knowledge bases changed from %s to %s",
				secutils.SanitizeForLog(fmt.Sprintf("%v", session.AgentConfig.KnowledgeBases)),
				secutils.SanitizeForLog(fmt.Sprintf("%v", request.KnowledgeBaseIDs)),
			)
		}
	}

	// Check if agent mode has changed
	currentAgentEnabled := session.AgentConfig.AgentModeEnabled
	if request.AgentEnabled != currentAgentEnabled {
		logger.Infof(ctx, "Agent mode changed from %v to %v", currentAgentEnabled, request.AgentEnabled)
		configChanged = true
	}
	currentWebSearchEnabled := session.AgentConfig.WebSearchEnabled
	if request.WebSearchEnabled != currentWebSearchEnabled {
		logger.Infof(ctx, "Web search mode changed from %v to %v", currentWebSearchEnabled, request.WebSearchEnabled)
		configChanged = true
	}
	summaryModelID := secutils.SanitizeForLog(request.SummaryModelID)
	if summaryModelID == "" {
		summaryModelID = session.SummaryModelID
	}
	if summaryModelID == "" && tenantInfo.ConversationConfig != nil {
		summaryModelID = tenantInfo.ConversationConfig.SummaryModelID
	}
	if summaryModelID != session.SummaryModelID {
		configChanged = true
		session.SummaryModelID = summaryModelID
	}

	// If configuration changed, clear context and update session
	if configChanged {
		logger.Warnf(ctx, "Configuration changed, clearing context for session: %s", sessionID)
		// Clear the LLM context to prevent contamination
		if err := h.sessionService.ClearContext(ctx, sessionID); err != nil {
			logger.Errorf(ctx, "Failed to clear context for session %s: %v", sessionID, err)
			// Continue anyway - this is not a fatal error
		}
		if err := h.sessionService.DeleteWebSearchTempKBState(ctx, sessionID); err != nil {
			logger.Errorf(ctx, "Failed to delete temp knowledge base for session %s: %v", sessionID, err)
			// Continue anyway - this is not a fatal error
		}
		session.AgentConfig.KnowledgeBases = secutils.SanitizeForLogArray(request.KnowledgeBaseIDs)
		session.AgentConfig.AgentModeEnabled = request.AgentEnabled
		session.AgentConfig.WebSearchEnabled = request.WebSearchEnabled
		session.SummaryModelID = secutils.SanitizeForLog(summaryModelID)
		// Persist the session changes
		if err := h.sessionService.UpdateSession(ctx, session); err != nil {
			logger.Errorf(ctx, "Failed to update session %s: %v", sessionID, err)
			c.Error(errors.NewInternalServerError("Failed to update session configuration"))
			return
		}
		logger.Infof(ctx, "Session configuration updated successfully for session: %s", sessionID)
	}

	// If Agent mode is disabled, delegate to KnowledgeQA
	if !request.AgentEnabled {
		logger.Infof(ctx, "Agent mode disabled, delegating to KnowledgeQA for session: %s", sessionID)

		// Use knowledge bases from request or session config
		knowledgeBaseIDs := secutils.SanitizeForLogArray(request.KnowledgeBaseIDs)
		if len(knowledgeBaseIDs) == 0 {
			knowledgeBaseIDs = session.AgentConfig.KnowledgeBases
		}

		// If still empty, use session default knowledge base
		if len(knowledgeBaseIDs) == 0 && session.KnowledgeBaseID != "" {
			knowledgeBaseIDs = []string{session.KnowledgeBaseID}
			logger.Infof(
				ctx,
				"Using session default knowledge base: %s",
				secutils.SanitizeForLog(session.KnowledgeBaseID),
			)
		}

		// Validate at least one knowledge base is available
		if len(knowledgeBaseIDs) == 0 {
			logger.Error(ctx, "No knowledge base available for delegation")
			c.Error(
				errors.NewBadRequestError("No knowledge base available. Please configure at least one knowledge base."),
			)
			return
		}

		logger.Infof(
			ctx,
			"Delegating to KnowledgeQA with knowledge bases: %s",
			secutils.SanitizeForLog(fmt.Sprintf("%v", knowledgeBaseIDs)),
		)

		// Use shared function to handle KnowledgeQA request (no title generation for AgentQA fallback)
		h.handleKnowledgeQARequest(
			ctx,
			c,
			session,
			secutils.SanitizeForLog(request.Query),
			secutils.SanitizeForLogArray(
				knowledgeBaseIDs,
			),
			assistantMessage,
			false,
			secutils.SanitizeForLog(request.SummaryModelID),
			request.WebSearchEnabled,
		)
		return
	}

	// Emit agent query event to create user message
	requestID := secutils.SanitizeForLog(c.GetString(types.RequestIDContextKey.String()))
	if err := event.Emit(ctx, event.Event{
		Type:      event.EventAgentQuery,
		SessionID: sessionID,
		RequestID: requestID,
		Data: event.AgentQueryData{
			SessionID: sessionID,
			Query:     secutils.SanitizeForLog(request.Query),
			RequestID: requestID,
		},
	}); err != nil {
		logger.Errorf(ctx, "Failed to emit agent query event: %v", err)
		return
	}

	// Set headers for SSE immediately
	setSSEHeaders(c)

	// Create user message
	if err := h.createUserMessage(ctx, sessionID, secutils.SanitizeForLog(request.Query), requestID); err != nil {
		c.Error(errors.NewInternalServerError(err.Error()))
		return
	}

	// Create assistant message (response)
	assistantMessagePtr, err := h.createAssistantMessage(ctx, assistantMessage)
	if err != nil {
		c.Error(errors.NewInternalServerError(err.Error()))
		return
	}
	assistantMessage = assistantMessagePtr

	logger.Infof(ctx, "Calling agent QA service, session ID: %s", sessionID)

	// Write initial agent_query event to StreamManager
	h.writeAgentQueryEvent(ctx, sessionID, assistantMessage.ID)

	eventBus := event.NewEventBus()
	// Create cancellable context for async operations
	asyncCtx, cancel := context.WithCancel(logger.CloneContext(ctx))
	// Create and subscribe stream handler
	h.setupStreamHandler(asyncCtx, sessionID, assistantMessage.ID, requestID, assistantMessage, eventBus)

	// Start async title generation if session has no title
	if session.Title == "" {
		logger.Infof(ctx, "Session has no title, starting async title generation, session ID: %s", sessionID)
		h.sessionService.GenerateTitleAsync(asyncCtx, session, secutils.SanitizeForLog(request.Query), eventBus)
	}

	// Register stop event handler to cancel the context
	h.setupStopEventHandler(eventBus, sessionID, assistantMessage, cancel)

	go func() {
		defer func() {
			if r := recover(); r != nil {
				buf := make([]byte, 1024)
				runtime.Stack(buf, true)
				logger.ErrorWithFields(asyncCtx,
					errors.NewInternalServerError(fmt.Sprintf("Agent QA service panicked: %v\n%s", r, string(buf))),
					map[string]interface{}{
						"session_id": sessionID,
					})
			}
			h.completeAssistantMessage(asyncCtx, assistantMessage)
			logger.Infof(asyncCtx, "Agent QA service completed for session: %s", sessionID)
		}()
		err := h.sessionService.AgentQA(
			asyncCtx,
			session,
			secutils.SanitizeForLog(request.Query),
			assistantMessage.ID,
			eventBus,
		)
		if err != nil {
			logger.ErrorWithFields(asyncCtx, err, nil)
			// Emit error event to dedicated EventBus
			eventBus.Emit(asyncCtx, event.Event{
				Type:      event.EventError,
				SessionID: sessionID,
				Data: event.ErrorData{
					Error:     err.Error(),
					Stage:     "agent_execution",
					SessionID: sessionID,
				},
			})
			return
		}
	}()

	// Handle events for SSE (blocking until connection is done)
	h.handleAgentEventsForSSE(ctx, c, sessionID, assistantMessage.ID, requestID, eventBus)
}

// handleKnowledgeQARequest handles a KnowledgeQA request with the given parameters
// This is a shared function used by both KnowledgeQA endpoint and AgentQA fallback
func (h *Handler) handleKnowledgeQARequest(
	ctx context.Context,
	c *gin.Context,
	session *types.Session,
	query string,
	knowledgeBaseIDs []string,
	assistantMessage *types.Message,
	generateTitle bool, // Whether to generate title if session has no title
	summaryModelID string, // Optional summary model ID (overrides session default)
	webSearchEnabled bool, // Whether web search is enabled
) {
	sessionID := session.ID
	requestID := getRequestID(c)

	// Create user message
	if err := h.createUserMessage(ctx, sessionID, query, requestID); err != nil {
		c.Error(errors.NewInternalServerError(err.Error()))
		return
	}

	// Create assistant message (response)
	if _, err := h.createAssistantMessage(ctx, assistantMessage); err != nil {
		c.Error(errors.NewInternalServerError(err.Error()))
		return
	}

	// Validate knowledge bases
	if len(knowledgeBaseIDs) == 0 {
		logger.Error(ctx, "No knowledge base ID available")
		c.Error(errors.NewBadRequestError("At least one knowledge base ID is required"))
		return
	}

	logger.Infof(ctx, "Using knowledge bases: %s", secutils.SanitizeForLog(fmt.Sprintf("%v", knowledgeBaseIDs)))

	// Set headers for SSE
	setSSEHeaders(c)

	// Write initial agent_query event to StreamManager
	h.writeAgentQueryEvent(ctx, sessionID, assistantMessage.ID)

	// Create dedicated EventBus for this request
	eventBus := event.NewEventBus()
	// Create cancellable context for async operations
	asyncCtx, cancel := context.WithCancel(logger.CloneContext(ctx))

	// Register stop event handler and setup stream handler
	h.setupStopEventHandler(eventBus, sessionID, assistantMessage, cancel)
	h.setupStreamHandler(asyncCtx, sessionID, assistantMessage.ID, requestID, assistantMessage, eventBus)

	// Generate title if needed
	if generateTitle && session.Title == "" {
		logger.Infof(ctx, "Session has no title, starting async title generation, session ID: %s", sessionID)
		h.sessionService.GenerateTitleAsync(asyncCtx, session, query, eventBus)
	}

	eventBus.On(event.EventAgentFinalAnswer, func(ctx context.Context, evt event.Event) error {
		data, ok := evt.Data.(event.AgentFinalAnswerData)
		if !ok {
			return nil
		}
		assistantMessage.Content += data.Content
		if data.Done {
			logger.Infof(asyncCtx, "Knowledge QA service completed for session: %s", sessionID)
			h.completeAssistantMessage(asyncCtx, assistantMessage)
			// Emit completion event when stream finishes
			if err := eventBus.Emit(asyncCtx, event.Event{
				Type:      event.EventAgentComplete,
				SessionID: sessionID,
				Data: event.AgentCompleteData{
					FinalAnswer: assistantMessage.Content,
				},
			}); err != nil {
				logger.Errorf(asyncCtx, "Failed to emit completion event: %v", err)
			}
			cancel() // Clean up context
			return nil
		}
		return nil
	})

	// Call service to perform knowledge QA (async, emits events)
	go func() {
		defer func() {
			if r := recover(); r != nil {
				buf := make([]byte, 10240)
				runtime.Stack(buf, true)
				logger.ErrorWithFields(
					asyncCtx,
					errors.NewInternalServerError(fmt.Sprintf("Knowledge QA service panicked: %v\n%s", r, string(buf))),
					nil,
				)
			}
		}()
		err := h.sessionService.KnowledgeQA(
			asyncCtx,
			session,
			query,
			knowledgeBaseIDs,
			assistantMessage.ID,
			summaryModelID,
			webSearchEnabled,
			eventBus,
		)
		if err != nil {
			logger.ErrorWithFields(asyncCtx, err, nil)
			// Emit error event to dedicated EventBus
			eventBus.Emit(asyncCtx, event.Event{
				Type:      event.EventError,
				SessionID: sessionID,
				Data: event.ErrorData{
					Error:     err.Error(),
					Stage:     "knowledge_qa_execution",
					SessionID: sessionID,
				},
			})
			return
		}
	}()

	// Handle events for SSE (blocking until connection is done)
	h.handleAgentEventsForSSE(ctx, c, sessionID, assistantMessage.ID, requestID, eventBus)
}

// completeAssistantMessage marks an assistant message as complete and updates it
func (h *Handler) completeAssistantMessage(ctx context.Context, assistantMessage *types.Message) {
	assistantMessage.UpdatedAt = time.Now()
	assistantMessage.IsCompleted = true
	_ = h.messageService.UpdateMessage(ctx, assistantMessage)
}
